using System;
using Sce.PlayStation.Core;
namespace Nyanimals
{
	public class CNyanimal : CAnimatedObject
	{
		CObjectStateMachine stateMachine;
        CEventListener eventListener;
        
        CPlayer player;
        private bool bHeld;
        
        private bool goingTowardsFood;
        private bool foodInRange;
        private CBaseFood foodToEat;
        
        CollisionSphere boundingSphere;
        // CollisionSphere listenRangeSphere; // for debugging only

        private bool racing;
        private CWayPoint destinationWayPoint;
        
        public bool Racing
        {
            get { return racing;}
            set { racing = value;}
        }
        public CWayPoint DestinationWayPoint
        {
            get { return destinationWayPoint;}
            set { destinationWayPoint = value;}
        }
        
        #region callback_functions
        private static int CalculateMaxExperience(int level)
        {
            return level * 10;
        }
        private static void ResetValues(CIntLimiter limiter, CNyanimal nyan, int newLevel)
        {
            int maxExp = CalculateMaxExperience(newLevel);
            limiter.Value = limiter.Min;            
            limiter.Max = maxExp;
        }
        private static void IncreaseRunningLevel(CLimiter<int> limiter, object obj)
        {
            CNyanimal nyan = (CNyanimal)obj;            
            Console.WriteLine(nyan.Name + " running level up!");
            
            nyan.runningLevel += 1;
            ResetValues(nyan.runningExperience, nyan, nyan.runningLevel);            
        }
        private static void IncreaseStaminaLevel(CLimiter<int> limiter, object obj)
        {
            CNyanimal nyan = (CNyanimal)obj;            
            Console.WriteLine(nyan.Name + " Stamina level up!");
            
            nyan.staminaLevel += 1;
            ResetValues(nyan.staminaExperience, nyan, nyan.staminaLevel);  
        }       
        #endregion
        
        #region STATS
        
        // behind the scenes
        float biteRate; // time between bites
        float listenRange; // 0 -> ??
        
        // game-related
        string name;
        
        // levels
        int runningLevel;
        int staminaLevel;
        
        // experience
        CIntLimiter runningExperience;
        CIntLimiter staminaExperience;
        
        // single value
        CFloatLimiter hunger; // 0 -> 100
        CFloatLimiter happiness; // -100 -> 100
        
        
        // for the display
        CStatDisplay statDisplay;
        CTextRender.TextData hungerText;        
        CTextRender.TextData StateText;        
        
        public string Name
        {
            get { return name;}
            set { name = value;}
        }
        public float BiteRate
        {
            get { return biteRate;}
            set { biteRate = value;}
        }
        
        public float Hunger
        {
            get { return hunger.Value;}
            set { hunger.Value = value;}
        }
        public int RunningEXP
        {
            get { return runningExperience.Value;}
            set { runningExperience.Value = value;}
        }
        public int RunningLevel
        {
            get { return runningLevel;}
            set { runningLevel = value;}
        }
        public int StaminaEXP
        {
            get { return staminaExperience.Value;}
            set { staminaExperience.Value = value;}
        }
        public int StaminaLevel
        {
            get { return staminaLevel;}
            set { staminaLevel = value;}
        }
        public int HungerStatus
        {
            get { return hunger.Status;}
        }
        public bool ShouldEat()
        {
            // our decision to eat depends on our hunger level
            // more hungry = bugger chance(e.g 20% full = 80% chance)            
            int eatChancePercent = (int)((1.0f - hunger.Lamda) * 100.0f);
            eatChancePercent /= 2;
            
            
            int roll = CRandom.Next(0, 100);
            bool result = roll < eatChancePercent;
            
            return result;
        }
        
        public float Happiness
        {
            get { return happiness.Value;}
            set { happiness.Value = value;}
        }
        public int HappinessStatus
        {
            get { return happiness.Status;}
        }
        
        #endregion
        
        public bool GoingTowardsFood
        {
            get { return goingTowardsFood;}
            set { goingTowardsFood = value;}
        }
        
        public CBaseFood FoodToEat
        {
            get { return foodToEat;}
            set { foodToEat = value;}
        }
        
        public CObjectStateMachine StateMachine
        {
            get { return stateMachine;}
        }
		
        public static void HandleEvents(CBaseObject obj, CBaseEvent e)
        {
            CNyanimal me = (CNyanimal)obj;
            switch(e.ID)
            {
            case EventID.WHISTLE_EVENT:
            {
                CWhistleEvent we = (CWhistleEvent)e;
                // Console.WriteLine("Whistle Event: Pos = " + we.Position.ToString());
                
                // only respond if we are in range
                // bool inRange = CMath.SphereToSphere(me.listenRangeSphere, me.player.BoundingSphere);
                float dist = CMath.Squared_Distence(me.Position, me.player.Position);
                bool inRange = dist <= (me.ListenRange * me.ListenRange);
                
                if(inRange)
                {
                    // to the walk state
                    me.stateMachine.ChangeState("walk");
                    CNyanimalWalkState walkState = (CNyanimalWalkState)me.stateMachine.CurState;
                    walkState.Destination = me.player.WhistleSphere;
                }
                
            } break;
            case EventID.SHOWSTAT_EVENT:
            {
                CShowStatEvent se = (CShowStatEvent)e;
                
                // if true, show the context else hide it
                me.statDisplay.Context.Active = se.Show;
                
            } break;
            case EventID.STARTRACE_EVENT:
            {
                CStartRaceEvent sr = (CStartRaceEvent)e;
                if(me.Racing == false)
                {
                    me.Racing = true;
                    me.DestinationWayPoint = sr.FirstPoint;
                    me.StateMachine.ChangeState("walk");
                    
                    CNyanimalWalkState walk = (CNyanimalWalkState)me.StateMachine.CurState;
                    CollisionSphere dest = walk.Destination;
                    walk.DeterminePosition(me.DestinationWayPoint, ref dest);
                    walk.Destination = dest;
                }
            } break;
            case EventID.BASE_EVENT:
            {
                // NOTE: shouldnt need to be getting here
                throw new NotImplementedException();
            } break;
                
            }
            }
        
		public CNyanimal()
		{
			boundingSphere = new CollisionSphere(Vector3.Zero, 1.0f);
            //listenRangeSphere = new CollisionSphere(Vector3.Zero, 1.0f);
            eventListener = new CEventListener(this, HandleEvents);
            hunger = new CFloatLimiter(0.0f, 100.0f, 50.0f);
            happiness = new CFloatLimiter(-100.0f, 100.0f, 0.0f);
            foodInRange = false;
            name = "INVALID NAME";
            
            statDisplay = new CStatDisplay(256, 128);
            
            
            // levels
            runningLevel = 0;
            staminaLevel = 0;
            
            // experience
            runningExperience = new CIntLimiter(0, 100, 0, IncreaseRunningLevel, null, this);
            staminaExperience = new CIntLimiter(0, 100, 0, IncreaseStaminaLevel, null, this);
            
		}
        
        public float ListenRange
        {
            get { return listenRange;}
            set { listenRange = value;}//listenRangeSphere.Radius = value;}
        }
		
        public CollisionSphere BoundingSphere
        {
            get { return boundingSphere;}
        }
		
		public bool Held
        {
            get { return bHeld;}
            set { bHeld = value;}
        }
        
         public override Vector3 Position
         {
                get 
                {
                    position = WorldMatrix.AxisW;
                    return position;
                }
                set 
                {
                    Matrix4 tempWorld = context.WorldMatrix;
                    Pivot = boundingSphere.Position = position = tempWorld.AxisW = value;
                    Context.WorldMatrix = tempWorld;
                }
         }
        
		public override void Init()
		{
			// player's animations
            AddAnimation(new tFrameLimit(0  , 0  , true , "bind"));
            AddAnimation(new tFrameLimit(1  , 60 , false, "spawn"));
            AddAnimation(new tFrameLimit(61 , 90 , true , "walk")); // prev: run
            AddAnimation(new tFrameLimit(91 , 120, true , "eat"));  // prev: attack, loop = false
            AddAnimation(new tFrameLimit(121, 144, true , "hold")); // prev: hit, loop = false
            AddAnimation(new tFrameLimit(145, 182, false, "death"));
            AddAnimation(new tFrameLimit(187, 228, true , "idle"));
            
			// create stateMachine and states
			stateMachine = new CObjectStateMachine((CBaseObject)this);
			stateMachine.CreateState(new CNyanimalIdleState("idle", this));
            stateMachine.CreateState(new CNyanimalHoldState("hold", this));
            stateMachine.CreateState(new CNyanimalWalkState("walk", this));
			stateMachine.CreateState(new CNyanimalEatState ("eat" , this));
            
            ListenRange = 50.0f;
            biteRate = 5.0f;
			
			// start in players idle
			stateMachine.ChangeState("idle");
            
            // register for events
            CEventManager.Instance.RegisterClient(ref eventListener);
            
            // to display our stats
            statDisplay.Init();
            statDisplay.OwningObject = this;
            hungerText = statDisplay.DrawText.AddText("Hunger: ", new CTextRender.Color(255, 255, 255, 255), 0, 0);
            StateText = statDisplay.DrawText.AddText("state", new CTextRender.Color(255, 255, 255, 255), 0, 32); 
            
            try
            {
                player = CObjectManager.Instance.Player;
            }
            catch
            {
                player = null;
            }
            bHeld = false;
		}
        
        public void Destroy()
        {
            // unregister for events
            CEventManager.Instance.UnregisterClient(ref eventListener);
        }
        		
		public override ObjectTypes Type
		{
			get { return ObjectTypes.Nyanimal_Object;}
		}
		
		public override void Input()
		{
			stateMachine.Input();
				
		}
		public override void Update(float deltaTime)
		{	
            stateMachine.Update(deltaTime);
            
            // override everything if we are being held
            if(bHeld)
            {
                // TODO: attach to a joint
                Matrix4 playerWorld = player.WorldMatrix;
                playerWorld.AxisW = Vector3.Zero;
                
                Pivot = player.Position + (playerWorld.AxisZ * 1) + (playerWorld.AxisY * 1);
                localMatrix = playerWorld;            
            }
            
            base.Update(deltaTime); 
            
            // decrease hunger over time
            hunger.Value -= 0.025f * deltaTime;
            hungerText.Text = "Hunger: " + hunger.Value.ToString();
            StateText.Text = stateMachine.CurState.StateName + " state";
            
            Position = WorldMatrix.AxisW;
            statDisplay.Update(deltaTime);
            
		}
		
		public override bool HandleCollision(ref CBaseObject Obj)
		{
            // dont bother checking if we are being held
            if(Held)
            {
                return false;
            }
            CollisionSphere otherSphere = null;
            switch(Obj.Type)
            {
            case ObjectTypes.Nyanimal_Object:
            {
                CNyanimal nyan = (CNyanimal)Obj;
                otherSphere = nyan.boundingSphere;               
                                
            } break;
            case ObjectTypes.Player_Object:
            {
                CPlayer player = (CPlayer)Obj;
                otherSphere = player.BoundingSphere;
                
                // check if the player is holding food AND in range
                CMovingObject heldByPlayer = player.HeldObject;
                if(heldByPlayer != null)
                {
                    // make sure its food
                    if(heldByPlayer.Type == ObjectTypes.Food_Object)
                    {
                        // check collision range
                        bool collidesWithHeldFood = CMath.SphereToSphere(boundingSphere, ((CBaseFood)heldByPlayer).BoundingSphere);
                        bool notEating = stateMachine.CurState.StateName != "eat";
                        if(collidesWithHeldFood && notEating)
                        {
                            // we can move to the eat state, so prepare for the transfer
                            goingTowardsFood = false;
                            foodInRange = false;
                            if(foodToEat != null)
                            {
                                foodToEat.Claim = false;
                            }                           
                            
                            // remove from player
                            foodToEat = (CBaseFood)player.HeldObject;
                            player.HeldObject = null;
                            player.StateMachine.ChangeState("idle");
                            
                            // give to nyanimal
                            foodToEat.ObjectHolding = this;
                            stateMachine.ChangeState("eat");
                            
                            // face the player
                            LookAt(player.Position, Vector3.UnitY);
                            
                            // tell display that hte player is no longer holdiong anything
                            CEventManager.Instance.QueueEvent(new CPickupObjectEvent(null));
                        }   
                    }
                }
                
            } break;
            case ObjectTypes.Food_Object:
            {
                CBaseFood food = (CBaseFood)Obj;
                
                // if in idle, we are eligable to eat
                if(stateMachine.CurState.StateName == "idle")
                {
                    if(food == foodToEat)
                    {
                        boundingSphere.Radius += 1.0f;
                        foodInRange = CMath.SphereToSphere(boundingSphere, food.BoundingSphere);
                        boundingSphere.Radius -= 1.0f;
                    }
                    
                }
                bool walkState = stateMachine.CurState.StateName == "walk";
                if(walkState || foodToEat != food)
                {
                    // if walk, push out of the way
                    otherSphere = food.BoundingSphere;
                }
            } break;
            }
            
            if(otherSphere != null)
            {
                // check bounding collition
                Vector3 newPos;
                bool boundingCollision = CMath.SphereToSphere(otherSphere, boundingSphere, out newPos);
                if(boundingCollision == true)
                {
                    newPos.Y = 0.0f; // lock to the plain
                    Position = Position + newPos;
                }
            }
			return false;
		}
        
        public void NewDecision()
        {
            // option chances(adds upto 100);
            //int remainingChance = 100;
            int eatChance = 0;          // TODO: implement this
            int lookforFoodChance = 0;  // TODO: implement this
            int sitChance = 0;          // TODO: implement this
            int idleChance = 50;  
            int walkChance = 100;
            
            // are we seeking food?
            bool eat = ShouldEat();
            eat = (hunger.Status != 1 && eat) || (foodInRange || goingTowardsFood);
            if(eat)
            {
                // determin if we can eat or we need to walk to food
                if(foodToEat != null)
                {
                    // we can eat
                    //Console.WriteLine("Eating!!!");
                    goingTowardsFood = false;
                    foodInRange = false;
                    foodToEat.Claim = false;
                    stateMachine.ChangeState("eat");
                }
                else
                {
                    CBaseFood food = CObjectManager.Instance.FindClosestFood(Position, FoodTypes.NyanBerry_Food);
                    if(food != null)
                    {
                        // move to closest food
                        //Console.WriteLine("To food!!!");
                        food.Claim = true;
                        foodToEat = food;
                        stateMachine.ChangeState("walk");
                        CNyanimalWalkState state = (CNyanimalWalkState)stateMachine.CurState;
                        state.Destination = food.BoundingSphere;
                        goingTowardsFood = true;
                    }
                    else
                    {
                        // we have no food to go to :(
                        //Console.WriteLine("No food found!");
                        eat = false;
                        goingTowardsFood = false;
                    }
                }
            }
            
            // NOTE: this is the opposite check to share a variable if there are not any food on the map
            if(eat == false)
            {            
                if(foodToEat != null)
                {
                    foodToEat.Claim = false;
                    foodToEat = null;
                }
                
                string newState;
                do
                {
                    // determin the next action
                    int decision = CRandom.Next(0, 100);
                    
                    if(decision < idleChance)
                    {
                        // Console.WriteLine("random Idle");
                       newState = "idle";
                    }
                    else if(decision < walkChance)
                    {
                        // Console.WriteLine("random walk");
                        newState = "walk";
                    }
                    else
                    {
                        Console.WriteLine("Bad place to be");
                        throw new NullReferenceException();
                    }
                }while(stateMachine.CurState.StateName == newState);
                
                // change to the new state
                stateMachine.ChangeState(newState);
                
                // give walkstate a direction
                if(newState == "walk")
                {
                    // since we are random, we need a direction to walk in
                    CNyanimalWalkState state = (CNyanimalWalkState)stateMachine.CurState;
                    state.RandomDirection();
                }
                
            }
        }
	}
}

